#!/usr/bin/env python
#!coding=utf-8

import socket as _socket
from collections import deque
from greeevent import *
_greenlet=__import__('greenlet')
getcurrent=_greenlet.getcurrent
greenlet=_greenlet.greenlet
from time import time
from random import randint
from sys import stdout

DEFAULT_MTU=1464
PACK_END_FLAG='\x00E\x00N\x00D\x00'
PACK_END_SIZE=len(PACK_END_FLAG)
DATA_SEG_FLAG='\xFFD\xFFA\xFFT\xFF\x00'
DATA_SEG_SIZE=len(DATA_SEG_FLAG)
GREECKET_NOOP=0.001
MAX_CONNECTION=3000
__all__=['Greepeer']

__version__='0.0.1a4'

randomId=lambda :('0000000000%s' % hex(randint(0,int(time()*1000)))[2:])[-11:-1]
    

class GreecketMeta(type):
    _greepeers=deque()#{gid:greecpeer}
    _newpeers=deque()
    _factory=None#BaseFactory()
    _eventpool=deque()
    _listenning=deque()
    _localcollaborate=None
    _listencoll=deque()
    _collaborates={}
    _peer4coll={}#greepeer id to collaborate id
    
    SO_FAMILY=_socket.AF_INET
    SO_TYPE=_socket.SOCK_STREAM
    SO_PROTO=_socket.IPPROTO_TCP
    
    @classmethod
    def Peers(cls):
        return tuple(GreecketMeta._greepeers)

    @staticmethod
    def getFactory():
        return GreecketMeta._factory    

class Greeobject(object):
    __metaclass__=GreecketMeta

    def __init__(self,*args,**kws):
        self._EventBinds={}

    def setSocketOptions(self,**kws):
        if 'SO_FAMILY' in kws:
            GreecketMeta.SO_FAMILY=kws['SO_FAMILY']
        if 'SO_TYPE' in kws:
            GreecketMeta.SO_TYPE=kws['SO_TYPE']
        if 'SO_PROTO' in kws:
            GreecketMeta.SO_PROTO=kws['SO_PROTO']

    def getEventBind(self,event):
        return self._EventBinds[event][:]  if event in self._EventBinds else []
        

    def bind(self,event,callback):#,binder=None):
        '''
        綁定事件響應函數
        {事件id:callback}
        '''
        if event not in self._EventBinds:
            self._EventBinds[event]=[callback]
        else:
            self._EventBinds[event].append(callback)

    def unbind(self,event,callback=None):
        if event in self._EventBinds and callback in self._EventBinds[event]:
            self._EventBinds[event].remove(callback)
        elif event in self._EventBinds and not callback:
            del self._EventBinds.pop(event)[:]

    @property
    def Factory(self):
        return GreecketMeta._factory

    @property
    def Machines(self):
        return tuple(GreecketMeta._machines)

    @property
    def Collaborate(self):
        return GreecketMeta._collaborate

    @Collaborate.setter
    def Collaborate(self,collaborate):
        GreecketMeta._collaborate=collaborate
        

class BaseGreecket(Greeobject):
    def __init__(self,collaborate=False):
        super(BaseGreecket,self).__init__()
        self._CallList=[self._accept,self._pmade,self._recv,self._send]
        if not GreecketMeta._factory:
            GreecketMeta._factory=self
        if collaborate:
            Collaborate()
        
    def _pclose(self,gpeer):
        GreecketMeta._greepeers.remove(gpeer)
        self.Factory.pushEvent(GreeEvent(GREE_CONNECTIONLOST,gpeer.RawPeer,gpeer))

    def _perror(self,error,gpeer):
        self.Factory.pushEvent(GreeEvent(GREE_ERROR,error,gpeer))
        
    def _dataRecv(self,data,gpeer):
        '''
        接收緩沖開頭4位為傳送數據的ID
        '''
        gpeer._RecvBuf.extend(data)
        self.Factory.pushEvent(GreeEvent(GREE_PACKRECEIVED,data,gpeer))
        i=gpeer._RecvBuf.find(PACK_END_FLAG)
        while i>=0:
            if i<4:
                del gpeer._RecvBuf[:]
                self.Factory.pushEvent(GreeEvent(GREE_ERROR,'[90002] data received error.',gpeer))
            elif i>4:
                s=str(gpeer._RecvBuf[4:i])
                del gpeer._RecvBuf[:i+PACK_END_SIZE]
                self.Factory.pushEvent(GreeEvent(GREE_DATARECEIVED,s,gpeer))
            else:
                del gpeer._RecvBuf[:]
            i=gpeer._RecvBuf.find(PACK_END_FLAG)


    def _collaborateRecv(self,data):
        pass

    def _recv(self):
        for g in tuple(GreecketMeta._greepeers):
            try:
                d=g.RawPeer.recv(g.PackSize*4)
                #self._rc+=len(d)
                stdout.write('\b')
            except _socket.error as e:
                d=e
            if not isinstance(d,str):
                if d.errno in (10054,10053) :#socket closed
                    self._pclose(g)
                elif not (d.errno==10035 or d.errno==11):#socket error
                    self._perror(e,g)
            elif d:
                self._dataRecv(d,g)
            else:
                self._pclose(g)

    def _accept(self):
        for gp in tuple(GreecketMeta._listenning):
            try:
                p=gp.RawPeer.accept()
                GreecketMeta._newpeers.append((p,gp))
                self.Factory.pushEvent(GreeEvent(GREE_CONNECTING,p,gp))
            except Exception as e:
                if not(e.errno==10035 or e.errno==11):
                    self.Factory.pushEvent(GreeEvent(GREE_ERROR,e,gp))

    def _pmade(self):
        while GreecketMeta._newpeers:#.append(p)
            p=GreecketMeta._newpeers.popleft()
            if isinstance(p,Greepeer):
                g1,g2=p,p
            else:
                g1=p[1]
                p=p[0][0]
                p.setblocking(0)
                g2=Greepeer(p)
            GreecketMeta._greepeers.append(g2)
            self.Factory.pushEvent(GreeEvent(GREE_CONNECTIONMADE,g2,g1))
            
            

    def _send(self):
        '''
        從傳送數據池中提取第一個數據進行傳送
        self._sendingData為None時表示沒有數據在傳送,非None時表示正在傳送的數據組ID
        傳送數據狀態,待機self._sendingData為None,等待傳送
        '''
        for gp in tuple(GreecketMeta._greepeers):
            try:
                gp._send()
            except Exception as e:
                del gp._SendBuf[:]
                self._pclose(gp)

    def _greenletLoop(self):
        pg=getcurrent().parent
        self._Active=True
        i=0
        while self._Active:
           self._CallList[i]()
           i=(i+1)%len(self._CallList)
           pg.switch()

    @staticmethod
    def connect(address):
        g=Greepeer()
        g.connect(address)
        return g

    @staticmethod
    def listen(address):
        g=Greepeer()
        g.listen(address)
        return g
        
    def _eventproccess(self,event=None):
        '''
        事件調度
        將推送來的事件按事件綁定表進行調用,先執行Greepeer和Machine綁定後再執行Factory/BaseFactory事件,如果事件設置skip標誌後將不再執行後面的綁定
        '''
        call=event.Peer.getEventBind(event.EventId)
        call.extend(self.getEventBind(event.EventId))
        call.reverse()
        while not event.Skip and call:
            c=call.pop()
            c(event)


class Greepeer(Greeobject):

    def __init__(self,socket=None,packsize=DEFAULT_MTU):
        super(Greepeer,self).__init__()
        self._Socket=socket
        self._PeerName=('',0)
        self._SockName=('',0)
        if  socket:
            self._PeerName=socket.getpeername()
            self._SockName=socket.getsockname()
        else:
            self._Socket=_socket.socket(GreecketMeta.SO_FAMILY,GreecketMeta.SO_TYPE,GreecketMeta.SO_PROTO)
        self._Socket.setsockopt(_socket.SOL_SOCKET,_socket.SO_REUSEADDR,1)
        self._PackSize=packsize
        self._RecvBuf=bytearray()
        self._SendBuf=bytearray()
        self._SendingData=bytearray()

    def sendBufferSize(self):
        return len(self._SendBuf)

    def getPeerName(self):
        return self._PeerName

    def getSockName(self):
        return self._SockName

    def listen(self,address):
        try:
            self._Socket.setblocking(0)
            self._Socket.bind(address)
            self._Socket.listen(MAX_CONNECTION)
            GreecketMeta._listenning.append(self)
            self.Factory.pushEvent(GreeEvent(GREE_LISTEN,self,self))
            self._PeerName=address
        except Exception as e:
            self.Factory.pushEvent(GreeEvent(GREE_ERROR,e,self))
        return self._Socket

    def connect(self,address):
        p=self._Socket
        try:
            p.connect(address)
            p.setblocking(0)
            GreecketMeta._newpeers.append(self)
            self._PeerName=address
            self.Factory.pushEvent(GreeEvent(GREE_CONNECTING,(p,address),self))
        except Exception as e:
            self.Factory.pushEvent(GreeEvent(GREE_ERROR,e,self))
            p=None
        return p

    def send(self,data):
        '''
        發送數據data
        發送數據為:(數據id)+數據+PACK_END_FLAG
        '''
        did=('%s%s' %('0'*8,hex(id(data))[2:]))[-8:].decode('hex')
        self._SendBuf.extend('%s%s%s' % (did,data,PACK_END_FLAG))
        return did

    def _send(self):
        if self._SendBuf:
            if not self._SendingData:
                self._SendingData=self._SendBuf[:4]
            d=str(self._SendBuf[:self._PackSize])
            r=self._Socket.send(d)
            stdout.write('\b')
            self._SendingData.extend(self._SendBuf[:r])
            sda= self._SendBuf[:r]
            del self._SendBuf[:r]
            self.Factory.pushEvent(GreeEvent(GREE_PACKSEND,str(sda),self))
            f=self._SendingData.find(PACK_END_FLAG)
            rr=len(self._SendingData)
            if rr>=f:#數據包發送完成
                da=self._SendingData[4:f]
                del self._SendingData[:f+PACK_END_SIZE]
                self.Factory.pushEvent(GreeEvent(GREE_DATASEND,da,self))
                

    def close(self):
        self._Socket.close()

    @property
    def PackSize(self):
        return self._PackSize
    
    @property
    def RawPeer(self):
        return self._Socket

class CollaborateBase(Greeobject):
    def __init__(self):
        super(CollaborateBase,self).__init__()
        self._CollaborateId='%s@%s' % (randomId(),_socket.gethostname())

    @property
    def Name(self):
        return self._CollaborateId

class Collaborate(CollaborateBase):
    def __init__(self):
        super(Collaborate,self).__init__()
        GreecketMeta._localcollaborate=self
        self.Factory.bind(GREE_CONNECTIONMADE,self._peerMade)
        self.Factory.bind(GREE_DATARECEIVED,self._dataRecv)
        self.Factory.bind(GREE_LISTEN,self._listen)
        self.Factory.bind(GREE_CONNECTIONLOST,self._close)
        self.Factory.bind(GREE_ERROR,self._error)
        self._NewPeers=deque()
        self._Peer4Coll={}
        self._RemoteColl4Local={}

    def _error(self,event):
        if isinstance(event.Peer,CollaboratePeer):
            return
        sp=self.isMadeByService(event.Peer)
        cp= sp if sp else self.isMadeByClient(event.Peer)
        if not (sp or cp):
            return
        if event.Peer in cp._NewPeers:
            cp._NewPeers.remove(event.Peer)
        if sp:
            self.Factory.pushEvent(GreeEvent(GREE_ERROR,event.Data,sp))
        if cp:
            self.Factory.pushEvent(GreeEvent(GREE_ERROR,event.Data,cp))

    def _close(self,event):
        '''
        鏈接關閉
        需要清理相關內存
        '''
        if isinstance(event.Peer,CollaboratePeer):
            return
        sp=self.isMadeByService(event.Peer)
        cp= sp if sp else self.isMadeByClient(event.Peer)
        np=False
        if cp and event.Peer in cp._NewPeers:
            sp._NewPeers.remove(event.Peer)
            np=True
        cp2=self._Peer4Coll.get(id(event.Peer))
        if cp2:
            cp2._Greepeers.remove(event.Peer)
            self.Factory.pushEvent(GreeEvent(GREE_CONNECTIONLOST,event.Peer,cp2))
        if sp:
            self.Factory.pushEvent(GreeEvent(GREE_CONNECTIONLOST,cp2,sp))
        if cp2 and  not cp2._Greepeers:
            self.Factory.pushEvent(GreeEvent(GREE_OFFLINE,event.Peer,cp2))
        if sp:
            self.Factory.pushEvent(GreeEvent(GREE_OFFLINE,cp2,sp))


    def _listen(self,event):
        for cp in GreecketMeta._listencoll:
            if event.Peer in cp._NewPeers:
                self.Factory.pushEvent(GreeEvent(GREE_LISTEN,event.Peer,cp))
                break

    def isMadeByService(self,peer):
        port=peer.getSockName()[1]#RawPeer.getsockname()[1]
        for cp in GreecketMeta._listencoll:
            if port==cp._Address[1]:
                return cp
        return None

    def isMadeByClient(self,peer):
        for cpid,cp in GreecketMeta._collaborates.items():
            if peer in cp._NewPeers:
                return cp
        return None

    def _peerMade(self,event):
        '''
        鏈接發生
        發生在服務端時,
            找到對應的監聽CollaboratePeer,推送GREE_CONNECTING事件
        發生在客戶端時
            發送CollaboratePeer Id到遠端鏈接
        綁定數據接收事件,接收遠端鏈接發過來的CollaboratePeer Id
        
        '''
        if isinstance(event.Peer,CollaboratePeer):
            return
        acp=self.isMadeByService(event.Data)
        event.Data.bind(GREE_PACKRECEIVED,self._newDataRecv)
        if acp:
            acp._NewPeers.append(event.Data)
            self.Factory.pushEvent(GreeEvent(GREE_CONNECTING,event.Data,acp))
        else:
            cp=self.isMadeByClient(event.Data)
            event.Data.send('%s%s%s' % (PACK_END_FLAG,cp._CollaborateId,DATA_SEG_FLAG))

    def _collaborateMade(self,collaboratepeer,remotecollid,greepeer,speer):
        cp,ss,peer=collaboratepeer,remotecollid,greepeer
        cp._NewPeers.remove(peer)
        if cp._Greepeers:
            if ss==cp._RemoteId:
                cp._Greepeers.append(peer)
                self._Peer4Coll[id(peer)]=cp
                self.Factory.pushEvent(GreeEvent(GREE_CONNECTIONMADE,peer,speer))
            else:
                peer.close()
                self.Factory.pushEvent(GreeEvent(GREE_ERROR,'[90001] Collaborate connect error.',speer))
        else:
            cp._Greepeers.append(peer)
            self._Peer4Coll[id(peer)]=cp
            self._RemoteColl4Local[ss]=cp
            cp._RemoteId=ss
            self.Factory.pushEvent(GreeEvent(GREE_ONLINE,cp,speer))


    def _newDataRecv(self,event):
        '''
        接收遠端鏈接發過來的CollaboratePeer Id
        遠端CollaboratePeer Id是否存在現有的CollaboratePeer中
        服務端
        客戶端
            檢查RomoteId是否為空
        不存在,新建CollaboratePeer,並發送CollaboratePeer Id
        完成後推送GREE_ONLINE事件
        '''
        if isinstance(event.Peer,CollaboratePeer):
            return
        s=event.Data[4:]
        event.Peer.unbind(GREE_PACKRECEIVED,self._newDataRecv)
        if s and s[:PACK_END_SIZE]==PACK_END_FLAG and DATA_SEG_FLAG in s:
            ss=s[PACK_END_SIZE:s.index(DATA_SEG_FLAG)]
            cp=self._RemoteColl4Local.get(ss)
            sp=self.isMadeByService(event.Peer)
            if not cp and sp:
                sp._NewPeers.remove(event.Peer)
                cp=CollaboratePeer()
                cp._NewPeers.append(event.Peer)
                event.Peer.send('%s%s%s' % (PACK_END_FLAG,cp._CollaborateId,DATA_SEG_FLAG))
            elif not cp and not sp:
                cp=self.isMadeByClient(event.Peer)
                sp=cp
            else:
                cp._NewPeers.append(event.Peer)
                sp=cp
            self._collaborateMade(cp,ss,event.Peer,sp)
        event.skip()
        
    def _dataRecv(self,event):
        if isinstance(event.Peer,CollaboratePeer):
            return
        cp=self._Peer4Coll.get(id(event.Peer))
        buf=cp._RecvBuf.get(event.Data[:4])
        i=int(event.Data[4:6].encode('hex'),16)
        t=int(event.Data[6:8].encode('hex'),16)
        if not buf:
            buf=[None]*t
        buf[i]=event.Data[8:]
        cp._RecvBuf[event.Data[:4]]=buf
        sp=self.isMadeByService(event.Peer)
        if None in buf:
            self.Factory.pushEvent(GreeEvent(GREE_PACKRECEIVED,event.Data,cp))
            if  sp:
                self.Factory.pushEvent(GreeEvent(GREE_PACKRECEIVED,(event.Data,cp),sp))
        else:
            cp._RecvBuf.pop(event.Data[:4])
            d=''.join(buf)
            self.Factory.pushEvent(GreeEvent(GREE_DATARECEIVED,d,cp))
            if sp:
                self.Factory.pushEvent(GreeEvent(GREE_DATARECEIVED,(d,cp),sp))
            
        

class CollaboratePeer(CollaborateBase):
    def __init__(self):
        '''
        多鏈接協同處理鏈接
        創建Collaboratepeer對象後自動使用協同處理模式
        只有兩端同時使用相同處理模式才能正常工作
        作為一個網絡鏈接對象peer來使用
        當listen或connect後工用方式就會固定
            作為服務後不可以再使用connect
        '''
        super(CollaboratePeer,self).__init__()
        self._RemoteId=''
        GreecketMeta._collaborates[self._CollaborateId]=self
        self._NewPeers=deque()
        self._Greepeers=deque()#Greepeer list
        self._AsServise=None
        self._Address=None
        self._RecvBuf={}

    def getName(self):
        return self._CollaborateId

    def _getOrderSendBuffer(self):
        ps=[(p.sendBufferSize,p) for p in self._Greepeers]
        ps.sort()
        return [p[1] for p in ps]

    def connect(self,address):
        if self in GreecketMeta._listencoll:
            return None
        if not self._Address:
            self._Address=address
        g=Greepeer()
        self._NewPeers.append(g)
        self.Factory.pushEvent(GreeEvent(GREE_CONNECTING,g,self))
        g.connect(self._Address)
        return g

    def listen(self,address):
        if self in GreecketMeta._listencoll or self._Greepeers or self._NewPeers:
            return None
        GreecketMeta._collaborates.pop(self._CollaborateId)
        GreecketMeta._listencoll.append(self)
        self._Address=address
        g=Greepeer()
        self._NewPeers.append(g)
        g.listen(address)
        return g

    def stop(self):
        '''
        關閉所有鏈接
        清除所有使用的對象及綁定的事件
        '''

    def send(self,data):
        '''
        傳送數據
        將數據按分組數量及最小發送數據包MTU分拆想相應的數組
        分組數據格式:數據id+當前數據包序號+總數據包數量+數據內容
        '''
        if not data or not self._Greepeers:
            return
        sz=len(data)
        ps=len(self._Greepeers)
        rds=range(0,sz,self._Greepeers[0].PackSize)
        m1=len(rds)
        rds.append(sz)
        m2=m1/ps
        m3=m1%ps
        rr=[m2]*ps
        
        for i in range(m3):
            rr[i]+=1
        j=0
        rr=filter(lambda x:x!=0,rr)
        gps=self._getOrderSendBuffer()
        if m2==0:
            gps=gps[:m1]
        did=('%s%s' %('0'*8,hex(id(data))[2:]))[-8:].decode('hex')
        psh=('0000%s' % hex(len(rr))[2:])[-4:].decode('hex')
        for i,r in enumerate(rr):
            d1,d2=rds[j],rds[r+j]
            ih=('0000%s' % hex(i)[2:])[-4:].decode('hex')
            s='%s%s%s%s' % (did,psh,ih,data[d1:d2])
            gps[i].send('%s%s%s%s' % (did,ih,psh,data[d1:d2]))
            j+=r
        
        
        

        
    
